using System;
using System.Collections;
using System.Globalization;

using Evaluant.OPath;
using Evaluant.OPath.Expressions;

namespace Evaluant.Uss.ObjectContext.Navigator
{
	internal class NavigationVisitor : OPathVisitor
    {
        #region Members

        private NumberFormatInfo _NumberFormatInfo;
		private object _NodeValue;
        private ArrayList _Roots;

        #endregion

        #region Ctor

        public NavigationVisitor()
        {
            _NodeValue = null;
            _Roots = new ArrayList();

            _NumberFormatInfo = new NumberFormatInfo();
            _NumberFormatInfo.NumberDecimalSeparator = ".";
        }

        #endregion

        #region Properties

        public object NodeValue
		{
			get { return _NodeValue; }
		}

		
        public ArrayList Roots
		{
			get {return _Roots; }
        }

        #endregion

        #region Visit

        public override void Visit(Path path)
		{
			Navigator navigator = new Navigator(_Roots);
			_NodeValue = navigator.SelectElements(path);
		}

		public override void Visit(Identifier identifier)
		{
		}

		public override void Visit(Function function)
		{
			function.Path.Accept(this);
			object pathValue = _NodeValue;

			switch(function.Type)
			{
				case FunctionEnum.Average :
					decimal avg = 0;
					foreach(object f in (object[])pathValue)
						avg += Convert.ToDecimal(f);

                    if (((object[])pathValue).Length > 0)
                        _NodeValue = avg / ((object[])pathValue).Length;
                    else
                        _NodeValue = avg;
					break;

				case FunctionEnum.Count :
					_NodeValue = Convert.ToDecimal(((object[])pathValue).Length);
					break;

				case FunctionEnum.Exists :
					_NodeValue = ((object[])pathValue).Length > 0;
					break;

				case FunctionEnum.IsNull :
					_NodeValue = ((object[])pathValue).Length == 0;
					break;

				case FunctionEnum.Max :
					decimal max = decimal.MinValue;

					foreach(object f in (object[])pathValue)
						max = Math.Max(max, Convert.ToDecimal(f));
					
					_NodeValue = max;
					break;

				case FunctionEnum.Min : 
					decimal min = decimal.MaxValue;

					foreach(object f in (object[])pathValue)
						min = Math.Min(min, Convert.ToDecimal(f));
					
					_NodeValue = min;
					break;

				case FunctionEnum.Sum :					
					decimal sum = 0;
					foreach(object f in (object[])pathValue)
						sum += Convert.ToDecimal(f);
					
					_NodeValue = sum;
					break;
			}
		}

		public override void Visit(BinaryOperator binaryop)
		{
			binaryop.LeftOperand.Accept(this);
			object left = _NodeValue;

			if(left.GetType().IsArray) // can be an array if it is a Path
			{
				object[] values = (object[])left;
				left = values.Length == 0 ? null : values[0];
			}

			binaryop.RightOperand.Accept(this);
			object right = _NodeValue;

			if(right.GetType().IsArray) // can be an array if it is a Path
			{
				object[] values = (object[])right;
				right = values.Length == 0 ? null : values[0];
			}

			switch(binaryop.Type)
			{
				case BinaryOperatorEnum.And : 
					if(left == null || right == null)
						_NodeValue = false;
					else
						_NodeValue = (bool)left && (bool)right;
					break;

				case BinaryOperatorEnum.Or :
					if(left == null)
					{
						_NodeValue = (bool)right;
						break;
					}

					if(right == null)
					{
						_NodeValue = (bool)left;
						break;
					}

					_NodeValue = (bool)left || (bool)right;

					break;

				case BinaryOperatorEnum.Div : 
					if(left == null || right == null)
					{
						_NodeValue = null;
						break;
					}

					_NodeValue = Convert.ToDecimal(left) / Convert.ToDecimal(right);
					break;

				case BinaryOperatorEnum.Equal : 
					if(left == null || right == null)
					{
						_NodeValue = (left == right);
						break;
					}

					if(left.GetType().IsEnum)
						_NodeValue = (left == Enum.Parse(left.GetType(), right.ToString()));
					else
						_NodeValue = Comparer.Default.Compare(left, Convert.ChangeType(right, left.GetType())) == 0;
					
					break;

				case BinaryOperatorEnum.Greater : 
					// Use the type of the left operand
					_NodeValue = Comparer.Default.Compare(left, Convert.ChangeType(right, left.GetType())) > 0;
					break;

				case BinaryOperatorEnum.GreaterOrEqual : 
					_NodeValue = Comparer.Default.Compare(left, Convert.ChangeType(right, left.GetType())) >= 0;
					break;

				case BinaryOperatorEnum.Lesser : 
					_NodeValue = Comparer.Default.Compare(left, Convert.ChangeType(right, left.GetType())) < 0;
					break;

				case BinaryOperatorEnum.LesserOrEqual : 
					_NodeValue = Comparer.Default.Compare(left, Convert.ChangeType(right, left.GetType())) <= 0;
					break;

				case BinaryOperatorEnum.Minus : 
					_NodeValue = Convert.ToDecimal(left) - Convert.ToDecimal(right);
					break;

				case BinaryOperatorEnum.Modulo : 
					_NodeValue = Convert.ToDecimal(left) % Convert.ToDecimal(right);
					break;

				case BinaryOperatorEnum.NotEqual : 
					if(left == null || right == null)
					{
						_NodeValue = left != right;
						break;
					}

					if(left.GetType().IsEnum)
						_NodeValue = (left == Enum.Parse(left.GetType(), right.ToString()));
					else
						_NodeValue = Comparer.Default.Compare(left, Convert.ChangeType(right, left.GetType())) != 0;

					break;

				case BinaryOperatorEnum.Plus : 
					_NodeValue = Convert.ToDecimal(left) + Convert.ToDecimal(right);
					break;

				case BinaryOperatorEnum.Times : 
					_NodeValue = Convert.ToDecimal(left) * Convert.ToDecimal(right);
					break;

				case BinaryOperatorEnum.Contains : 
					_NodeValue = Convert.ToString(left).IndexOf(Convert.ToString(right)) != -1;
					break;

				case BinaryOperatorEnum.BeginsWith : 
					_NodeValue = Convert.ToString(left).StartsWith(Convert.ToString(right));
					break;

				case BinaryOperatorEnum.EndsWith : 
					_NodeValue = Convert.ToString(left).EndsWith(Convert.ToString(right));
					break;

			}
		}

		public override void Visit(UnaryOperator unaryop)
		{
			unaryop.Operand.Accept(this);

			switch(unaryop.Type)
			{
				case UnaryOperatorEnum.Not :
					_NodeValue = !(bool)_NodeValue;
					break;

				case UnaryOperatorEnum.Minus :
					_NodeValue = -Convert.ToDecimal(_NodeValue);
					break;
			}
		}

		public override void Visit(Call call)
		{
            //switch (call.Name)
            //{
            //    case "id":
            //        _NodeValue = false;
            //        for (int i = 0; i < call.Operands.Count; i++)
            //        {
            //            NavigationVisitor checker = new NavigationVisitor();
            //            call.Operands[i].Accept(checker);
            //            if (((Entity)_Roots[0]).Id == (string)checker._NodeValue)
            //            {
            //                _NodeValue = true;
            //                return;
            //            }
            //        }
            //        break;
            //}
		}

		public override void Visit(Value val)
		{
			switch(val.Type)
			{
				case ValueEnum.Boolean :
					_NodeValue = bool.Parse(val.Text);
					break;

				case ValueEnum.Date :
					_NodeValue = DateTime.Parse(val.Text);
					break;

				case ValueEnum.Float :
					_NodeValue = decimal.Parse(val.Text, _NumberFormatInfo);
					break;

				case ValueEnum.Integer :
					// Use float parser as the binary operations will be applied as floats
					_NodeValue = decimal.Parse(val.Text);
					break;

				case ValueEnum.String :
					_NodeValue = val.Text;
					break;
			}
        }

        #endregion
    }
}
